In [21]:
import itertools as it
import time

Zebra Puzzle


In [22]:
def imright(h1, h2):
    """Return if h2 is right next to h1."""
    return h2-h1 == 1
def nextto(h1, h2):
    """Return if h1 and h2 is next to each other."""
    return abs(h2-h1) == 1
def zebra_puzzle():
    """Return a tuple (WATER, ZEBRA) indicating their houses numbers."""
    houses = first, _, middle, _, _ = range(1,6)
    orderings = list(it.permutations(houses))
    return next((WATER, ZEBRA)
            for (red, green, ivory, yellow, blue) in orderings
            if imright(green, ivory)
            for (Englishman, Spaniard, Ukranian, Japanese, Norwegian) in orderings
            if Englishman is red
            if Norwegian is first
            if nextto(Norwegian, blue)
            for (dog, snails, fox, horse, ZEBRA) in orderings
            if Spaniard is dog
            for (coffee, tea, milk, oj, WATER) in orderings
            if coffee is green
            if Ukranian is tea
            if milk is middle
            for (OldGold, Kools, Chesterfields, LuckyStrike, Parliaments) in orderings
            if OldGold is snails
            if Kools is yellow
            if nextto(Chesterfields, fox)
            if nextto(Kools, horse)
            if LuckyStrike is oj
            if Japanese is Parliaments
            )
def timedcall(fn, *args):
    """Call function with args; return the time in seconds and result"""
    t0 = time.clock()
    result = fn(*args)
    t1 = time.clock()
    return t1-t0, result
print "Computation Time:{0} (Water, Zebra):{1}".format(*timedcall(zebra_puzzle))


Computation Time:0.005814 (Water, Zebra):(1, 4)

Learning how to use "yield"


In [23]:
def ints(start, end = None):
    """Generate integers in the order start, start+1, ... end if end is not None;
       Otherwise keep generating."""
    i = start
    while i <= end or end is None:
        yield i
        i += 1

def all_ints():
    """Generate integers in the order 0, +1, -1, +2, -2, +3, -3, ..."""
    yield 0
    for v in ints(1):
        yield +v
        yield -v

test_fn = all_ints()
assert [test_fn.next() for _ in xrange(5)] == [0, 1, -1, 2, -2]

Analysing performance


In [24]:
def average(numbers):
    """Return the average (arithmetic mean) of a sequence of numbers."""
    return sum(numbers) / float(len(numbers))

def timedcalls(n, fn, *args):
    """Call fn(*args) repeatedly: n times if n is an int, or up to
    n seconds if n is a float; return the min, avg, and max time"""
    if isinstance(n, int):
        times = [timedcall(fn, *args)[0] for _ in xrange(n)]
    else:
        times = []
        while sum(times) <= n:
            times.append(timedcall(fn, *args)[0])
    return min(times), average(times), max(times)

print "It takes around {1:.3f} seconds to find the answer.".format(*timedcalls(10, zebra_puzzle))


It takes around 0.006 seconds to find the answer.

In [25]:
def c(sequence):
    """Generate items in sequence. c.start is the number of sequences start.
    c.count is the number of items generated."""
    c.start += 1
    for item in sequence:
        c.count += 1
        yield item
def zebra_puzzle():
    """Return a tuple (WATER, ZEBRA) indicating their houses numbers."""
    houses = first, _, middle, _, _ = range(1,6)
    orderings = list(it.permutations(houses))
    return next((WATER, ZEBRA)
            for (red, green, ivory, yellow, blue) in c(orderings)
            if imright(green, ivory)
            for (Englishman, Spaniard, Ukranian, Japanese, Norwegian) in c(orderings)
            if Englishman is red
            if Norwegian is first
            if nextto(Norwegian, blue)
            for (dog, snails, fox, horse, ZEBRA) in c(orderings)
            if Spaniard is dog
            for (coffee, tea, milk, oj, WATER) in c(orderings)
            if coffee is green
            if Ukranian is tea
            if milk is middle
            for (OldGold, Kools, Chesterfields, LuckyStrike, Parliaments) in c(orderings)
            if OldGold is snails
            if Kools is yellow
            if nextto(Chesterfields, fox)
            if nextto(Kools, horse)
            if LuckyStrike is oj
            if Japanese is Parliaments
            )

def instrument_fn(fn, *args):
    c.start, c.count = 0, 0
    result = fn(*args)
    print "{} got {} with {:5d} iters over {:7d} items.".format(fn.__name__, result, c.start, c.count)
    
instrument_fn(zebra_puzzle)


zebra_puzzle got (1, 4) with   298 iters over   35536 items.

Cryptarithmetic


In [13]:
import string, re, itertools

def solve(formula):
    """Given a formula like 'ODD + ODD == EVEN', fill in digits to solve it.
    Input formula is a string; output is a digit-filled-in string or None."""
    for f in fill_in(formula):
        if valid(f):
            return f
    
def fill_in(formula):
    "Generate all possible fillings-in of letters in formula with digits."
    letters = ''.join(set(re.findall('[A-Z]', formula)))
    for digits in itertools.permutations('1234567890', len(letters)):
        table = string.maketrans(letters, ''.join(digits))
        yield formula.translate(table)
    
def valid(f):
    """Formula f is valid if and only if it has no 
    numbers with leading zero, and evals true."""
    try: 
        return not re.search(r'\b0[0-9]', f) and eval(f) is True
    except ArithmeticError:
        return False
    
def test():
    examples = """ODD + ODD == EVEN
                  A**2 + B**2 == C**2
                  APPLE + PEN == APPLEPEN"""
    for example in examples.strip().split("\n"):
        print solve(example.strip())

test()


655 + 655 == 1310
3**2 + 4**2 == 5**2
None

In [47]:
import cProfile
cProfile.run("test()")


655 + 655 == 1310
3**2 + 4**2 == 5**2
None
         508164 function calls in 1.914 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    57776    0.130    0.000    0.242    0.000 <ipython-input-46-f25448a19df8>:10(fill_in)
    57773    0.107    0.000    1.641    0.000 <ipython-input-46-f25448a19df8>:17(valid)
        1    0.000    0.000    1.943    1.943 <ipython-input-46-f25448a19df8>:25(test)
        3    0.059    0.020    1.942    0.647 <ipython-input-46-f25448a19df8>:3(solve)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        4    0.000    0.000    0.000    0.000 __init__.py:193(dumps)
        1    0.000    0.000    0.000    0.000 attrsettr.py:35(__getattr__)
        4    0.000    0.000    0.000    0.000 encoder.py:101(__init__)
        4    0.000    0.000    0.000    0.000 encoder.py:186(encode)
        4    0.000    0.000    0.000    0.000 encoder.py:212(iterencode)
       28    0.000    0.000    0.000    0.000 encoder.py:33(encode_basestring)
        2    0.000    0.000    0.000    0.000 encoder.py:37(replace)
        1    0.000    0.000    0.000    0.000 hmac.py:100(_current)
        1    0.000    0.000    0.000    0.000 hmac.py:119(hexdigest)
        1    0.000    0.000    0.000    0.000 hmac.py:30(__init__)
        4    0.000    0.000    0.000    0.000 hmac.py:83(update)
        1    0.000    0.000    0.000    0.000 hmac.py:88(copy)
        7    0.000    0.000    0.000    0.000 iostream.py:102(_check_mp_mode)
        1    0.000    0.000    0.000    0.000 iostream.py:123(_flush_from_subprocesses)
        1    0.000    0.000    0.001    0.001 iostream.py:151(flush)
        6    0.000    0.000    0.001    0.000 iostream.py:207(write)
        1    0.000    0.000    0.000    0.000 iostream.py:238(_flush_buffer)
        1    0.000    0.000    0.000    0.000 iostream.py:247(_new_buffer)
        8    0.000    0.000    0.000    0.000 iostream.py:93(_is_master_process)
        1    0.000    0.000    0.000    0.000 iostream.py:96(_is_master_thread)
        4    0.000    0.000    0.000    0.000 jsonapi.py:31(dumps)
        1    0.000    0.000    0.000    0.000 jsonutil.py:75(date_default)
        1    0.000    0.000    0.000    0.000 poll.py:77(poll)
        1    0.000    0.000    0.000    0.000 py3compat.py:12(no_code)
    57773    0.073    0.000    0.340    0.000 re.py:143(search)
        3    0.000    0.000    0.000    0.000 re.py:173(findall)
    57776    0.133    0.000    0.133    0.000 re.py:230(_compile)
        1    0.000    0.000    0.000    0.000 session.py:206(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:211(extract_header)
        1    0.000    0.000    0.000    0.000 session.py:452(msg_id)
        1    0.000    0.000    0.000    0.000 session.py:504(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:507(msg)
        1    0.000    0.000    0.000    0.000 session.py:526(sign)
        1    0.000    0.000    0.000    0.000 session.py:541(serialize)
        1    0.000    0.000    0.001    0.001 session.py:600(send)
        4    0.000    0.000    0.000    0.000 session.py:94(<lambda>)
        1    0.000    0.000    0.000    0.000 socket.py:289(send_multipart)
        1    0.000    0.000    0.000    0.000 threading.py:1143(currentThread)
        1    0.000    0.000    0.000    0.000 threading.py:974(ident)
       13    0.000    0.000    0.000    0.000 traitlets.py:420(__get__)
        6    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        1    0.000    0.000    0.000    0.000 uuid.py:103(__init__)
        1    0.000    0.000    0.000    0.000 uuid.py:199(__str__)
        1    0.000    0.000    0.000    0.000 uuid.py:582(uuid4)
        6    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        1    0.000    0.000    0.000    0.000 {built-in method now}
    45677    1.164    0.000    1.193    0.000 {eval}
        1    0.000    0.000    0.000    0.000 {getattr}
        1    0.000    0.000    0.000    0.000 {hasattr}
       32    0.000    0.000    0.000    0.000 {isinstance}
       11    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {locals}
        1    0.000    0.000    0.000    0.000 {map}
        1    0.000    0.000    0.000    0.000 {max}
        3    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'close' of '_io.StringIO' objects}
        3    0.000    0.000    0.000    0.000 {method 'copy' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'copy' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'count' of 'list' objects}
        6    0.000    0.000    0.000    0.000 {method 'decode' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'digest' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        3    0.000    0.000    0.000    0.000 {method 'encode' of 'unicode' objects}
        2    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}
        3    0.000    0.000    0.000    0.000 {method 'findall' of '_sre.SRE_Pattern' objects}
        1    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.StringIO' objects}
        2    0.000    0.000    0.000    0.000 {method 'group' of '_sre.SRE_Match' objects}
        1    0.000    0.000    0.000    0.000 {method 'hexdigest' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'isoformat' of 'datetime.datetime' objects}
    57780    0.027    0.000    0.027    0.000 {method 'join' of 'str' objects}
    57773    0.133    0.000    0.133    0.000 {method 'search' of '_sre.SRE_Pattern' objects}
        7    0.000    0.000    0.000    0.000 {method 'send' of 'zmq.backend.cython.socket.Socket' objects}
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}
        4    0.000    0.000    0.000    0.000 {method 'strip' of 'str' objects}
       28    0.000    0.000    0.000    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}
    57773    0.022    0.000    0.022    0.000 {method 'translate' of 'str' objects}
        5    0.000    0.000    0.000    0.000 {method 'update' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'upper' of 'str' objects}
        6    0.000    0.000    0.000    0.000 {method 'write' of '_io.StringIO' objects}
        9    0.000    0.000    0.000    0.000 {posix.getpid}
        1    0.000    0.000    0.000    0.000 {posix.urandom}
        1    0.000    0.000    0.000    0.000 {range}
    57773    0.063    0.000    0.063    0.000 {strop.maketrans}
        1    0.000    0.000    0.000    0.000 {thread.get_ident}
        6    0.000    0.000    0.000    0.000 {time.time}
        1    0.000    0.000    0.000    0.000 {zmq.backend.cython._poll.zmq_poll}


From the profiling, we could see that 'valid' takes far more time than 'fill_in'. In order to speed the funciton of 'valid', we should compile formula into function instead of using 'eval' too many times. For example, compiling 'YOU == ME*2' into 'lambda Y,M,E,U,O: (U+10\O+100*Y) == (E+10*M)**2'.


In [7]:
def faster_solve(formula):
    """Given a formula like 'ODD + ODD == EVEN', fill in digits to solve it.
    Input formula is a string; output is a digit-filled-in string or None.
    This version precompiles the formula; only one eval per formula."""
    f, letters = compile_formula(formula)
    for digits in itertools.permutations(range(0,10), len(letters)):
        try:
            if f(*digits):
                table = string.maketrans(letters, ''.join(map(str, digits)))
                return formula.translate(table)
        except ArithmeticError:
            pass

def compile_word(word):
    if word.isupper():
        terms = [("{}*{}".format(10**i, d))
                 for (i, d) in enumerate(word[::-1])]
        return "({})".format("+".join(terms))
    else:
        return word
    
def compile_formula(formula, verbose=False):
    """Compile formula into a function. Also return letters found, as a str, in same order as param of function.
    For example, 'YOU == ME**2' returns (lambda Y,M,E,U,O: (U+10\O+100*Y) == (E+10M)*2), 'YMEUO'"""
    letters = ''.join(set(re.findall(r'[A-Z]', formula)))
    parms = ', '.join(letters)
    tokens = map(compile_word, re.split(r'([A-Z]+)', formula))
    body = ' '.join(tokens)
    f = "lambda {}: {}".format(parms, body)
    if verbose: print f
    return eval(f), letters

In [18]:
def test():
    examples = """ODD + ODD == EVEN
                  A**2 + B**2 == C**2
                  APPLE + PEN == APPLEPEN"""
    for example in examples.strip().split("\n"):
        print faster_solve(example.strip())

test()


655 + 655 == 1310
3**2 + 4**2 == 5**2
None

In [19]:
import cProfile
cProfile.run("test()")


655 + 655 == 1310
3**2 + 4**2 == 5**2
None
         30667 function calls in 0.071 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.019    0.006    0.069    0.023 <ipython-input-15-0cd8d071958a>:1(faster_solve)
       21    0.000    0.000    0.000    0.000 <ipython-input-15-0cd8d071958a>:14(compile_word)
        3    0.000    0.000    0.001    0.000 <ipython-input-15-0cd8d071958a>:22(compile_formula)
        1    0.000    0.000    0.071    0.071 <ipython-input-18-cf2b40cb09d4>:1(test)
    30240    0.050    0.000    0.050    0.000 <string>:1(<lambda>)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        4    0.000    0.000    0.001    0.000 __init__.py:193(dumps)
        1    0.000    0.000    0.000    0.000 attrsettr.py:35(__getattr__)
        4    0.000    0.000    0.000    0.000 encoder.py:101(__init__)
        4    0.000    0.000    0.001    0.000 encoder.py:186(encode)
        4    0.000    0.000    0.001    0.000 encoder.py:212(iterencode)
       28    0.000    0.000    0.001    0.000 encoder.py:33(encode_basestring)
        2    0.000    0.000    0.000    0.000 encoder.py:37(replace)
        1    0.000    0.000    0.000    0.000 hmac.py:100(_current)
        1    0.000    0.000    0.000    0.000 hmac.py:119(hexdigest)
        1    0.000    0.000    0.000    0.000 hmac.py:30(__init__)
        4    0.000    0.000    0.000    0.000 hmac.py:83(update)
        1    0.000    0.000    0.000    0.000 hmac.py:88(copy)
        7    0.000    0.000    0.000    0.000 iostream.py:102(_check_mp_mode)
        1    0.000    0.000    0.000    0.000 iostream.py:123(_flush_from_subprocesses)
        1    0.000    0.000    0.002    0.002 iostream.py:151(flush)
        6    0.000    0.000    0.002    0.000 iostream.py:207(write)
        1    0.000    0.000    0.000    0.000 iostream.py:238(_flush_buffer)
        1    0.000    0.000    0.000    0.000 iostream.py:247(_new_buffer)
        8    0.000    0.000    0.000    0.000 iostream.py:93(_is_master_process)
        1    0.000    0.000    0.000    0.000 iostream.py:96(_is_master_thread)
        4    0.000    0.000    0.001    0.000 jsonapi.py:31(dumps)
        1    0.000    0.000    0.000    0.000 jsonutil.py:75(date_default)
        1    0.000    0.000    0.000    0.000 poll.py:77(poll)
        1    0.000    0.000    0.000    0.000 py3compat.py:12(no_code)
        3    0.000    0.000    0.000    0.000 re.py:168(split)
        3    0.000    0.000    0.000    0.000 re.py:173(findall)
        6    0.000    0.000    0.000    0.000 re.py:230(_compile)
        1    0.000    0.000    0.000    0.000 session.py:206(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:211(extract_header)
        1    0.000    0.000    0.000    0.000 session.py:452(msg_id)
        1    0.000    0.000    0.000    0.000 session.py:504(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:507(msg)
        1    0.000    0.000    0.000    0.000 session.py:526(sign)
        1    0.000    0.000    0.001    0.001 session.py:541(serialize)
        1    0.000    0.000    0.001    0.001 session.py:600(send)
        4    0.000    0.000    0.001    0.000 session.py:94(<lambda>)
        1    0.000    0.000    0.000    0.000 socket.py:289(send_multipart)
        1    0.000    0.000    0.000    0.000 threading.py:1143(currentThread)
        1    0.000    0.000    0.000    0.000 threading.py:974(ident)
       13    0.000    0.000    0.000    0.000 traitlets.py:420(__get__)
        6    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        1    0.000    0.000    0.000    0.000 uuid.py:103(__init__)
        1    0.000    0.000    0.000    0.000 uuid.py:199(__str__)
        1    0.000    0.000    0.000    0.000 uuid.py:582(uuid4)
        6    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        1    0.000    0.000    0.000    0.000 {built-in method now}
        3    0.000    0.000    0.000    0.000 {eval}
        1    0.000    0.000    0.000    0.000 {getattr}
        1    0.000    0.000    0.000    0.000 {hasattr}
       32    0.000    0.000    0.000    0.000 {isinstance}
       11    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {locals}
        6    0.000    0.000    0.000    0.000 {map}
        1    0.000    0.000    0.000    0.000 {max}
        3    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'close' of '_io.StringIO' objects}
        3    0.000    0.000    0.000    0.000 {method 'copy' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'copy' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'count' of 'list' objects}
        6    0.000    0.000    0.000    0.000 {method 'decode' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'digest' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        3    0.000    0.000    0.000    0.000 {method 'encode' of 'unicode' objects}
        2    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}
        3    0.000    0.000    0.000    0.000 {method 'findall' of '_sre.SRE_Pattern' objects}
       41    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.StringIO' objects}
        2    0.000    0.000    0.000    0.000 {method 'group' of '_sre.SRE_Match' objects}
        1    0.000    0.000    0.000    0.000 {method 'hexdigest' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'isoformat' of 'datetime.datetime' objects}
       21    0.000    0.000    0.000    0.000 {method 'isupper' of 'str' objects}
       24    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}
        7    0.000    0.000    0.000    0.000 {method 'send' of 'zmq.backend.cython.socket.Socket' objects}
        3    0.000    0.000    0.000    0.000 {method 'split' of '_sre.SRE_Pattern' objects}
        1    0.000    0.000    0.000    0.000 {method 'split' of 'str' objects}
        4    0.000    0.000    0.000    0.000 {method 'strip' of 'str' objects}
       28    0.001    0.000    0.001    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}
        2    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}
        5    0.000    0.000    0.000    0.000 {method 'update' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'upper' of 'str' objects}
        6    0.000    0.000    0.000    0.000 {method 'write' of '_io.StringIO' objects}
        9    0.000    0.000    0.000    0.000 {posix.getpid}
        1    0.000    0.000    0.000    0.000 {posix.urandom}
        4    0.000    0.000    0.000    0.000 {range}
        2    0.000    0.000    0.000    0.000 {strop.maketrans}
        1    0.000    0.000    0.000    0.000 {thread.get_ident}
        6    0.000    0.000    0.000    0.000 {time.time}
        1    0.000    0.000    0.000    0.000 {zmq.backend.cython._poll.zmq_poll}


Problem Set

No Leading Zeros


In [20]:
def faster_solve(formula):
    """Given a formula like 'ODD + ODD == EVEN', fill in digits to solve it.
    Input formula is a string; output is a digit-filled-in string or None.
    This version precompiles the formula; only one eval per formula."""
    f, letters = compile_formula(formula)
    for digits in itertools.permutations(range(0,10), len(letters)):
        try:
            if f(*digits):
                table = string.maketrans(letters, ''.join(map(str, digits)))
                return formula.translate(table)
        except ArithmeticError:
            pass

def compile_word(word):
    if word.isupper():
        terms = [("{}*{}".format(10**i, d))
                 for (i, d) in enumerate(word[::-1])]
        return "({})".format("+".join(terms))
    else:
        return word
    
def compile_formula(formula, verbose=False):
    """Compile formula into a function. Also return letters found, as a str, in same order as param of function.
    For example, 'YOU == ME**2' returns (lambda Y,M,E,U,O: (U+10\O+100*Y) == (E+10M)*2), 'YMEUO'"""
    letters = ''.join(set(re.findall(r'[A-Z]', formula)))
    firstletters = set(re.findall(r'\b([A-Z])[A-Z]', formula))
    parms = ', '.join(letters)
    tokens = map(compile_word, re.split(r'([A-Z]+)', formula))
    body = ' '.join(tokens)
    if firstletters:
        body = "{} and {}".format(" and ".join([L + " != 0" for L in firstletters]), body)
    f = "lambda {}: {}".format(parms, body)
    if verbose: print f
    return eval(f), letters

def test():
    assert faster_solve('A + B == BA') == None # should NOT return '1 + 0 == 01'
    assert faster_solve('YOU == ME**2') == ('289 == 17**2' or '576 == 24**2' or '841 == 29**2')
    assert faster_solve('X / X == X') == '1 / 1 == 1'
    return 'tests pass'
test()


Out[20]:
'tests pass'

Floor Puzzle


In [31]:
def floor_puzzle():
    floors = bottom, _, _, _, top = range(1, 6)
    lives = itertools.permutations(floors, 5)
    return next([Hopper, Kay, Liskov, Perlis, Ritchie]
               for (Hopper, Kay, Liskov, Perlis, Ritchie) in lives
               if Hopper is not top
               if Kay is not bottom
               if Liskov not in [bottom, top]
               if Perlis > Kay
               if abs(Ritchie-Liskov) is not 1
               if abs(Liskov-Kay) is not 1)
print floor_puzzle()


[3, 2, 4, 5, 1]

Subpalindrome


In [59]:
def isPalindrome(text):
    text = text.lower()
    L = len(text)
    if L % 2 == 0:
        return text[:L/2] == text[-1:-1+L/2:-1]
    else:
        return text[:L/2] == text[-1:L/2:-1]
    
def longest_subpalindrome_slice(text):
    "Return (i, j) such that text[i:j] is the longest palindrome in text."
    if text == "":
        return (0, 0)
    else:
        L = len(text)
        ans, Len = (0, 1), 1
        for i in xrange(L):
            j = i + Len
            while j <= L:
                if isPalindrome(text[i:j]) and j - i > Len:
                    ans = (i, j)
                    Len = j - i
                j += 1
        return ans

def test():
    L = longest_subpalindrome_slice
    assert L('racecar') == (0, 7)
    assert L('Racecar') == (0, 7)
    assert L('RacecarX') == (0, 7)
    assert L('Race carr') == (7, 9)
    assert L('') == (0, 0)
    assert L('something rac e car going') == (8,21)
    assert L('xxxxx') == (0, 5)
    assert L('Mad am I ma dam.') == (0, 15)
    return 'tests pass'

import cProfile
cProfile.run("test()")


         882 function calls in 0.002 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      288    0.001    0.000    0.002    0.000 <ipython-input-59-843fedbd07f1>:1(isPalindrome)
        1    0.000    0.000    0.002    0.002 <ipython-input-59-843fedbd07f1>:25(test)
        8    0.000    0.000    0.002    0.000 <ipython-input-59-843fedbd07f1>:9(longest_subpalindrome_slice)
        1    0.000    0.000    0.002    0.002 <string>:1(<module>)
      295    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      288    0.000    0.000    0.000    0.000 {method 'lower' of 'str' objects}



In [60]:
def longest_subpalindrome_slice(text):
    "Return (i, j) such that text[i:j] is the longest palindrome in text."
    if text == "": return (0, 0)
    def length(slice):
        a, b = slice
        return b-a
    candidates = [grow(text, start, end)
                 for start in range(len(text))
                 for end in (start, start+1)]
    return max(candidates, key=length)

def grow(text, start, end):
    "Start with a 0- or 1- length palindrome; try to grow a bigger one."
    L = len(text)
    while (start > 0 and end < L
           and text[start-1].upper() == text[end].upper()):
        start -= 1
        end += 1
    return (start, end)

cProfile.run("test()")


         802 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.001    0.001 <ipython-input-59-843fedbd07f1>:25(test)
        8    0.000    0.000    0.001    0.000 <ipython-input-60-c98d0e96f68d>:1(longest_subpalindrome_slice)
      154    0.000    0.000    0.000    0.000 <ipython-input-60-c98d0e96f68d>:12(grow)
      154    0.000    0.000    0.000    0.000 <ipython-input-60-c98d0e96f68d>:4(length)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
      161    0.000    0.000    0.000    0.000 {len}
        7    0.000    0.000    0.000    0.000 {max}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      308    0.000    0.000    0.000    0.000 {method 'upper' of 'str' objects}
        7    0.000    0.000    0.000    0.000 {range}



In [ ]: